home *** CD-ROM | disk | FTP | other *** search
/ Tech Arsenal 1 / Tech Arsenal (Arsenal Computer).ISO / tek-13 / dte5_1.zip / ED.C < prev    next >
C/C++ Source or Header  |  1991-02-06  |  45KB  |  1,459 lines

  1. /*
  2.  * Written by Douglas Thomson (1989/1990)
  3.  *
  4.  * This source code is released into the public domain.
  5.  */
  6.  
  7. /*
  8.  * Name:    dte - Doug's Text Editor program - main editor module
  9.  * Purpose: This file contains the main editor module, and a number of the
  10.  *           smaller miscellaneous editing commands.
  11.  *          It also contains the code for dispatching commands.
  12.  * File:    ed.c
  13.  * Author:  Douglas Thomson
  14.  * System:  this file is intended to be system-independent
  15.  * Date:    October 1, 1989
  16.  * I/O:     file being edited
  17.  *          files read or written
  18.  *          user commands and prompts
  19.  * Notes:   see the file "dte.doc" for general program documentation
  20.  */
  21.  
  22. #ifdef HPXL
  23. #include "commonh"      /* common types for all dte modules */
  24. #include "globalh"      /* global variables */
  25. #include "findreph"     /* find and replace command prototypes */
  26. #include "blockh"       /* block marking, moving etc prototypes */
  27. #include "windowh"      /* opening and sizing windows, help */
  28. #include "utilsh"       /* miscellaneous commonly used routines */
  29. #else
  30. #include "common.h"     /* common types for all dte modules */
  31. #include "global.h"     /* global variables */
  32. #include "findrep.h"    /* find and replace command prototypes */
  33. #include "block.h"      /* block marking, moving etc prototypes */
  34. #include "window.h"     /* opening and sizing windows, help */
  35. #include "utils.h"      /* miscellaneous commonly used routines */
  36. #endif
  37. #include <time.h>       /* for auto-saving */
  38.  
  39. /*
  40.  * prototypes for all functions in this file
  41.  */
  42. void quit ARGS((windows *window));
  43. void tab_key ARGS((windows *window));
  44. void insert ARGS((windows *window, int c, int new_line));
  45. void move_up ARGS((windows *window));
  46. void move_down ARGS((windows *window));
  47. void move_left ARGS((windows *window));
  48. void move_right ARGS((windows *window));
  49. void word_left ARGS((windows *window));
  50. void word_right ARGS((windows *window));
  51. void word_delete ARGS((windows *window));
  52. void char_del_left ARGS((windows *window));
  53. void line_kill ARGS((windows *window));
  54. void char_del_under ARGS((windows *window));
  55. void eol_kill ARGS((windows *window));
  56. void reminder ARGS((char *mess));
  57. void goto_left ARGS((windows *window));
  58. void goto_right ARGS((windows *window));
  59. void goto_top ARGS((windows *window));
  60. void goto_bottom ARGS((windows *window));
  61. void set_tabstop ARGS((void));
  62. int command ARGS((windows *window));
  63. void editor ARGS((int argc, char *argv[]));
  64.  
  65. /*
  66.  * Name:    quit
  67.  * Purpose: To close the current window without saving the current file.
  68.  * Date:    October 1, 1989
  69.  * Passed:  window: information allowing access to the current window
  70.  * Notes:   If the file has been modified but not saved, then the user is
  71.  *           given a second chance before the changes are discarded.
  72.  *          Note that this is only necessary if this is the last window
  73.  *           that refers to the file. If another window still refers to
  74.  *           the file, then the check can be left until later.
  75.  */
  76. void quit(window)
  77. windows *window;
  78. {
  79.     if (window->file_info->modified && window->file_info->ref_count == 1) {
  80.         set_prompt("Abandon changes? (y/n): ", 1);
  81.         if (display(get_yn, 1) != A_YES) {
  82.             return;
  83.         }
  84.     }
  85.  
  86.     /*
  87.      * If the user decided to abandon changes, then the recovery file (if
  88.      *  one exists) is now obsolete.
  89.      */
  90.     if (g_status.recovery[0]) {
  91.         hw_unlink(g_status.recovery);
  92.         g_status.recovery[0] = '\0';
  93.     }
  94.  
  95.     /*
  96.      * remove window, allocate screen lines to other windows etc
  97.      */
  98.     finish(window);
  99. }
  100.  
  101. /*
  102.  * Name:    tab_key
  103.  * Purpose: To make the necessary changes after the user types the tab key.
  104.  * Date:    October 1, 1989
  105.  * Passed:  window: information allowing access to the current window
  106.  * Notes:   If in insert mode, then this function simply puts the required
  107.  *           number of spaces back into the input stream, so as far as the
  108.  *           editor is concerned the tab is ignored, and the user then typed
  109.  *           the spaces instead.
  110.  *          If not in insert mode, then tab simply moves the cursor right
  111.  *           the required distance.
  112.  */
  113. void tab_key(window)
  114. windows *window;
  115. {
  116.     int spaces;  /* the spaces to move to the next tab stop */
  117.  
  118.     /*
  119.      * work out the number of spaces to the next tab stop
  120.      */
  121.     spaces = g_status.tab_size - (window->ccol % g_status.tab_size);
  122.  
  123.     if (g_status.insert) {
  124.         /*
  125.          * pretend the user actually typed the spaces. All the work will
  126.          *  be done by the insert function.
  127.          */
  128.         while (spaces--) {
  129.             c_uninput(' ');
  130.         }
  131.     }
  132.     else {
  133.         /*
  134.          * advance the cursor without changing the text underneath
  135.          */
  136.         window->ccol += spaces;
  137.  
  138.         /*
  139.          * make sure the cursor stays on the screen
  140.          */
  141.         if (window->ccol >= g_display.ncols) {
  142.             window->ccol = g_display.ncols-1;
  143.         }
  144.     }
  145. }
  146.  
  147. /*
  148.  * Name:    insert
  149.  * Purpose: To make the necessary changes after the user has typed a normal
  150.  *           printable character (or a carriage return)
  151.  * Date:    October 1, 1989
  152.  * Passed:  window:   information allowing access to the current window
  153.  *          c:        the character just typed
  154.  *          new_line: TRUE if carriage return, FALSE if insert line
  155.  */
  156. void insert(window, c, new_line)
  157. windows *window;
  158. int c;
  159. int new_line;
  160. {
  161.     text_ptr source;    /* source for block move to make room for c */
  162.     text_ptr dest;      /* destination for block move */
  163.     long number;        /* number of characters to be moved */
  164.     int len;            /* length of current line */
  165.     int pad;            /* padding to add if cursor beyond end of line */
  166.     int add;            /* characters to be added (usually 1 in insert mode) */
  167.     int cr;             /* does current line end in \n? */
  168.     int i;              /* counter for adding autoindenting */
  169.     text_ptr prev;      /* previous lines scanned for autoindent */
  170.  
  171.     /*
  172.      * first check we have room on the screen - although the editor can
  173.      *  cope with lines wider than the screen, we do not want to
  174.      *  encourage them!
  175.      */
  176.     if (window->ccol >= g_display.ncols-1 && c != '\n') {
  177.         error(WARNING, "cannot insert more characters");
  178.         return;
  179.     }
  180.  
  181.     /*
  182.      * if necessary, copy the current line into the line buffer. Making
  183.      *  small changes to the entire file is too slow, so only the current
  184.      *  line is affected until the cursor moves to another line.
  185.      */
  186.     copy_line(window);
  187.  
  188.     /*
  189.      * work out how many characters need to be inserted
  190.      */
  191.     len = linelen(g_status.line_buff);
  192.     if (g_status.line_buff[len] == '\n') {
  193.         cr = 1;
  194.     }
  195.     else {
  196.         cr = 0;
  197.     }
  198.     if (window->ccol > len) {  /* padding required */
  199.         pad = window->ccol - len;
  200.     }
  201.     else {
  202.         pad = 0;
  203.     }
  204.     if (c == '\n') {
  205.         add = 0;
  206.         /*
  207.          * indentation is only required if we are in the right mode,
  208.          *  the user typed <CR>, and if there is not space followed
  209.          *  by something after the cursor.
  210.          */
  211.         if (g_status.indent && new_line && (window->ccol >= len ||
  212.                 g_status.line_buff[window->ccol] != ' ')) {
  213.             /*
  214.              * autoindentation is required. Match the indentation of
  215.              *  the first line above that is not blank.
  216.              */
  217.             add = first_non_blank(g_status.line_buff);
  218.             if (g_status.line_buff[add] == '\n' ||
  219.                     g_status.line_buff[add] == '\0') {
  220.                 prev = window->cursor;
  221.                 while ((prev = find_prev(prev)) != NULL) {
  222.                     add = first_non_blank((char *)prev);
  223.                     if (prev[add] != '\n') {
  224.                         break;
  225.                     }
  226.                 }
  227.             }
  228.         }
  229.         ++add; /* carriage return is always inserted, even in overwrite */
  230.     }
  231.     else if (g_status.insert || window->ccol >= len) {
  232.         /*
  233.          * inserted characters, or overwritten characters at the end of
  234.          *  the line, are inserted.
  235.          */
  236.         add = 1;
  237.     }
  238.     else {
  239.         /*
  240.          * If the character is not a carriage return, and the cursor is
  241.          *  in the middle of the line, and we are not in insert mode,
  242.          *  then the current character is overwritten by the new one,
  243.          *  and no extra space is required.
  244.          */
  245.         add = 0;
  246.     }
  247.  
  248.     /*
  249.      * check that current line would not get too long. Note that there must
  250.      *  be space for both the old line and any indentation, so the maximum
  251.      *  allowed line length (BUFF_SIZE) should be at least twice the
  252.      *  actual screen line length.
  253.      */
  254.     if (len + pad + add + cr >= BUFF_SIZE) {
  255.         error(WARNING, "no more room to add");
  256.         return;
  257.     }
  258.  
  259.     /*
  260.      * all clear to add new character!
  261.      */
  262.  
  263.     /*
  264.      * move character to make room for whatever needs to be inserted
  265.      */
  266.     source = g_status.line_buff + window->ccol - pad;
  267.     dest = source + pad + add;
  268.     number = len + pad - window->ccol + 1 + cr;
  269.     hw_move(dest, source, number);
  270.  
  271.     /*
  272.      * fix marks (such as block begin/end) so that they remain in the
  273.      *  correct place after the move
  274.      */
  275.     fix_marks(window, source, (long) (pad + add));
  276.  
  277.     /*
  278.      * if padding was required, then put in the required spaces
  279.      */
  280.     while (pad--) {
  281.         *source++ = ' ';
  282.     }
  283.  
  284.     /*
  285.      * now place the new character (which was included in the "add" count)
  286.      */
  287.     *source++ = c;
  288.     --add;
  289.  
  290.     /*
  291.      * now put in the autoindent characters
  292.      */
  293.     for (i=0; i < add; i++) {
  294.         *source++ = ' ';
  295.     }
  296.  
  297.     if (c == '\n') {
  298.         /*
  299.          * the line has been split. This is a special case, since the
  300.          *  current line now has (or may have) a carriage return in the
  301.          *  middle of it, which is not normally allowed. Hence we must
  302.          *  restore the situation to a safe state.
  303.          */
  304.         un_copy_line(window);
  305.  
  306.         /*
  307.          * make sure the line below the cursor is visible
  308.          */
  309.         if (window->cline == window->bottom_line) {
  310.             if (window->cline > window->top_line) {
  311.                 scroll_down(window);
  312.             }
  313.         }
  314.  
  315.         /*
  316.          * give the display routine a hint about a way of updating the
  317.          *  screen that is likely to be efficient.
  318.          */
  319.         if (window->cline < window->bottom_line) {
  320.             window_scroll_down(window->cline+1, window->bottom_line);
  321.         }
  322.  
  323.         /*
  324.          * If the cursor is to move down to the next line, then update
  325.          *  the line and column appropriately.
  326.          */
  327.         if (new_line) {
  328.             window->ccol = add;
  329.             window->cursor = find_next(window->cursor);
  330.             if (window->cline < window->bottom_line) {
  331.                 window->cline++;
  332.             }
  333.         }
  334.     }
  335.     else {
  336.         /*
  337.          * This is a normal character in a normal part of the screen.
  338.          *  Simply advance the cursor and output the character.
  339.          */
  340.         xygoto(window->ccol, window->cline);
  341.         if (g_status.insert && window->ccol < len) {
  342.             c_insert();
  343.         }
  344.         c_output(c);
  345.         window->ccol++;
  346.     }
  347.  
  348.     /*
  349.      * record that file has been modified (this is only necessary here
  350.      *  if in overwrite mode [where pad and add can both be 0])
  351.      */
  352.     window->file_info->modified = TRUE;
  353.     if (!g_status.unsaved) {
  354.         g_status.save_time = time(NULL);
  355.         g_status.unsaved = TRUE;
  356.     }
  357. }
  358.  
  359. /*
  360.  * Name:    move_up
  361.  * Purpose: To move the cursor one line up the screen.
  362.  * Date:    October 1, 1989
  363.  * Passed:  window:   information allowing access to the current window
  364.  * Notes:   If the cursor is at the top of the window, then the file must
  365.  *           be scrolled down.
  366.  *          If the cursor is already on the first line of the file, then
  367.  *           this command can be ignored.
  368.  */
  369. void move_up(window)
  370. windows *window;
  371. {
  372.     text_ptr p;   /* the previous line on the screen */
  373.  
  374.     un_copy_line(window);
  375.  
  376.     /*
  377.      * if no previous line, give up
  378.      */
  379.     if ((p = find_prev(window->cursor)) == NULL) {
  380.         return;
  381.     }
  382.  
  383.     if (window->cline == window->top_line) {
  384.         window_scroll_down(window->top_line, window->bottom_line);
  385.     }
  386.     else {
  387.         --window->cline;        /* simply move cursor */
  388.     }
  389.  
  390.     window->cursor = p;
  391. }
  392.  
  393. /*
  394.  * Name:    move_down
  395.  * Purpose: To move the cursor one line down the screen.
  396.  * Date:    October 1, 1989
  397.  * Passed:  window:   information allowing access to the current window
  398.  * Notes:   If the cursor is at the bottom of the window, then the file must
  399.  *           be scrolled up.
  400.  *          If the cursor is already on the last line of the file, then
  401.  *           this command can be ignored.
  402.  */
  403. void move_down(window)
  404. windows *window;
  405. {
  406.     text_ptr p;
  407.  
  408.     un_copy_line(window);
  409.     if ((p = find_next(window->cursor)) == NULL) {
  410.         return;
  411.     }
  412.     if (window->cline == window->bottom_line) {
  413.         window_scroll_up(window->top_line, window->bottom_line);
  414.     }
  415.     else {
  416.         ++window->cline;
  417.     }
  418.     window->cursor = p;
  419. }
  420.  
  421. /*
  422.  * Name:    move_left
  423.  * Purpose: To move the cursor one character to the left
  424.  * Date:    October 1, 1989
  425.  * Passed:  window:   information allowing access to the current window
  426.  * Notes:   If the cursor is already at the left of the screen, then
  427.  *           this command is ignored.
  428.  */
  429. void move_left(window)
  430. windows *window;
  431. {
  432.     if (window->ccol > 0) {
  433.         --window->ccol;
  434.     }
  435. }
  436.  
  437. /*
  438.  * Name:    move_right
  439.  * Purpose: To move the cursor one character to the right
  440.  * Date:    October 1, 1989
  441.  * Passed:  window:   information allowing access to the current window
  442.  * Notes:   If the cursor is already at the right of the screen, then
  443.  *           this command is ignored.
  444.  *          It is quite OK to move the cursor beyond the rightmost
  445.  *           character in the line itself.
  446.  */
  447. void move_right(window)
  448. windows *window;
  449. {
  450.     if (window->ccol < g_display.ncols-1) {
  451.         ++window->ccol;
  452.     }
  453. }
  454.  
  455. /*
  456.  * Name:    word_left
  457.  * Purpose: To move the cursor one word to the left.
  458.  * Date:    October 1, 1989
  459.  * Passed:  window:   information allowing access to the current window
  460.  * Notes:   If the cursor is at the left of the line, then move the
  461.  *           end of the previous line.
  462.  *          If the cursor is beyond the end of the line, then move back
  463.  *           to the end of the line.
  464.  *          Words are considered strings of letters, numbers and underscores,
  465.  *           which must be separated by other characters.
  466.  */
  467. void word_left(window)
  468. windows *window;
  469. {
  470.     text_ptr p;   /* previous line in file */
  471.     int len;      /* length of current line */
  472.  
  473.     /*
  474.      * get current line in buffer so we can play with it
  475.      */
  476.     copy_line(window);
  477.  
  478.     if (window->ccol > (len = linelen(g_status.line_buff))) {
  479.         /*
  480.          * cursor beyond end of line
  481.          */
  482.         window->ccol = len;
  483.     }
  484.     else if (window->ccol == 0) {
  485.         /*
  486.          * cursor at start of line
  487.          */
  488.         un_copy_line(window);
  489.         if ((p = find_prev(window->cursor)) != NULL) {
  490.             if (window->cline == window->top_line) {
  491.                 scroll_up(window);
  492.             }
  493.             window->cursor = p;
  494.             --window->cline;
  495.             window->ccol = linelen(window->cursor);
  496.         }
  497.     }
  498.     else {
  499.         /*
  500.          * normal search for word. Do not consider character under
  501.          *  cursor, so that two word left commands in a row will keep
  502.          *  moving the cursor.
  503.          */
  504.         --window->ccol;
  505.  
  506.         /*
  507.          * scan for something that IS part of a word
  508.          */
  509.         for (;;) {
  510.             if (myisalnum(g_status.line_buff[window->ccol])) {
  511.                 break;
  512.             }
  513.             if (window->ccol == 0) {
  514.                 break;
  515.             }
  516.             --window->ccol;
  517.         }
  518.  
  519.         /*
  520.          * now scan for something that is NOT part of a word
  521.          */
  522.         if (window->ccol > 0) {
  523.             for (;;) {
  524.                 if (!myisalnum(g_status.line_buff[window->ccol])) {
  525.                     /*
  526.                      * we have found something that is NOT part of a word,
  527.                      *  so the thing after it must be the start of a word.
  528.                      */
  529.                     ++window->ccol;
  530.                     break;
  531.                 }
  532.                 if (window->ccol == 0) {
  533.                     break;
  534.                 }
  535.                 --window->ccol;
  536.             }
  537.         }
  538.     }
  539. }
  540.  
  541. /*
  542.  * Name:    word_right
  543.  * Purpose: To move the cursor one word to the right.
  544.  * Date:    October 1, 1989
  545.  * Passed:  window:   information allowing access to the current window
  546.  * Notes:   If the cursor is at the right of the line, then move the
  547.  *           start of the next line.
  548.  *          Words are considered strings of letters, numbers and underscores,
  549.  *           which must be separated by other characters.
  550.  */
  551. void word_right(window)
  552. windows *window;
  553. {
  554.     int len;     /* length of this line */
  555.     text_ptr p;  /* next line */
  556.  
  557.     copy_line(window);
  558.  
  559.     if (window->ccol >= (len = linelen(g_status.line_buff))) {
  560.         /*
  561.          * at or beyond end of line, so move to start of next line
  562.          */
  563.         un_copy_line(window);
  564.         if ((p = find_next(window->cursor)) != NULL) {
  565.             if (window->cline == window->bottom_line) {
  566.                 scroll_down(window);
  567.             }
  568.             window->cursor = p;
  569.             ++window->cline;
  570.             window->ccol = 0;
  571.         }
  572.     }
  573.     else {
  574.         /*
  575.          * normal word right - see comments in word_left
  576.          */
  577.         for (;;) {
  578.             if (!myisalnum(g_status.line_buff[window->ccol])) {
  579.                 break;
  580.             }
  581.             if (window->ccol == len) {
  582.                 break;
  583.             }
  584.             ++window->ccol;
  585.         }
  586.         if (window->ccol < len) {
  587.             for (;;) {
  588.                 if (myisalnum(g_status.line_buff[window->ccol])) {
  589.                     break;
  590.                 }
  591.                 if (window->ccol == len) {
  592.                     break;
  593.                 }
  594.                 ++window->ccol;
  595.             }
  596.         }
  597.     }
  598. }
  599.  
  600. /*
  601.  * Name:    word_delete
  602.  * Purpose: To delete from the cursor to the start of the next word.
  603.  * Date:    October 1, 1989
  604.  * Passed:  window:   information allowing access to the current window
  605.  * Notes:   If the cursor is at the right of the line, then combine the
  606.  *           current line with the next one, leaving the cursor where it
  607.  *           is.
  608.  *          If the cursor is on an alphanumeric character, then all
  609.  *           subsequent alphanumeric characters are deleted.
  610.  *          If the cursor is on a space, then all subsequent spaces
  611.  *           are deleted.
  612.  *          If the cursor is on a punctuation character, then all
  613.  *           subsequent punctuation characters are deleted.
  614.  */
  615. void word_delete(window)
  616. windows *window;
  617. {
  618.     int len;            /* length of current line */
  619.     int start;          /* column that next word starts in */
  620.     text_ptr source;    /* source for block move to delete word */
  621.     text_ptr dest;      /* destination for block move */
  622.     long number;        /* number of characters to move */
  623.     int del_len;        /* number of characters to delete */
  624.     text_ptr p;         /* next line in file */
  625.     int pad;            /* padding spaces required */
  626.     int cr;             /* does current line end with carriage return? */
  627.     int alpha;          /* is the cursor char alphanumeric? */
  628.  
  629.     copy_line(window);
  630.     if (window->ccol >= (len = linelen(g_status.line_buff))) {
  631.         /*
  632.          * we need to combine with the next line, if any
  633.          */
  634.         if ((p = find_next(window->cursor)) != NULL) {
  635.             /*
  636.              * add padding if required
  637.              */
  638.             if (g_status.line_buff[len] == '\n') {
  639.                 cr = 1;
  640.             }
  641.             else {
  642.                 cr = 0;
  643.             }
  644.             if (window->ccol > len) {
  645.                 pad = window->ccol - len;
  646.             }
  647.             else {
  648.                 pad = 0;
  649.             }
  650.  
  651.             /*
  652.              * check room to combine lines
  653.              */
  654.             if (len + pad + cr + linelen(p) >= BUFF_SIZE) {
  655.                 error(WARNING, "cannot combine lines");
  656.                 return;
  657.             }
  658.  
  659.             /*
  660.              * do the move, fixing any marks
  661.              */
  662.             source = g_status.line_buff + window->ccol - pad;
  663.             dest = source + pad;
  664.             number = len + pad - window->ccol + 1 + cr;
  665.             hw_move(dest, source, number);
  666.             fix_marks(window, source, (long) pad);
  667.  
  668.             /*
  669.              * insert the padding
  670.              */
  671.             while (pad--) {
  672.                 *source++ = ' ';
  673.             }
  674.  
  675.             /*
  676.              * remove the \n separating the two lines
  677.              */
  678.             if (*source == '\n') {
  679.                 *source = '\0';
  680.             }
  681.  
  682.             /*
  683.              * let un_copy_line finish off the merge and adjust
  684.              *  marks etc.
  685.              */
  686.             un_copy_line(window);
  687.  
  688.             /*
  689.              * give display a hint about how to update the screen
  690.              */
  691.             if (window->cline < window->bottom_line) {
  692.                 window_scroll_up(window->cline+1, window->bottom_line);
  693.             }
  694.         }
  695.     }
  696.     else {
  697.         /*
  698.          * normal word delete
  699.          *
  700.          * find the start of the next word
  701.          */
  702.         start = window->ccol;
  703.         if (g_status.line_buff[start] == ' ') {
  704.             /*
  705.              * the cursor was on a space, so eat all consecutive spaces
  706.              *  from the cursor onwards.
  707.              */
  708.             for (;;) {
  709.                 if (g_status.line_buff[start] != ' ') {
  710.                     break;
  711.                 }
  712.                 ++start;
  713.             }
  714.         }
  715.         else {
  716.             /*
  717.              * eat all consecutive characters in the same class (spaces
  718.              *  are considered to be in the same class as the cursor
  719.              *  character)
  720.              */
  721.             alpha = myisalnum(g_status.line_buff[start++]);
  722.             for (;;) {
  723.                 if (start == len) {
  724.                     break;
  725.                 }
  726.                 if (g_status.line_buff[start] == ' ') {
  727.                     /*
  728.                      * the next character that is not a space will
  729.                      *  end the delete
  730.                      */
  731.                     alpha = -1;
  732.                 }
  733.                 else if (alpha != myisalnum(g_status.line_buff[start])) {
  734.                     if (g_status.line_buff[start] != ' ') {
  735.                         break;
  736.                     }
  737.                 }
  738.                 ++start;
  739.             }
  740.         }
  741.  
  742.         /*
  743.          * move text to delete word, and fix marks
  744.          */
  745.         source = g_status.line_buff + start;
  746.         dest = g_status.line_buff + window->ccol;
  747.         number = strlen(g_status.line_buff) - start + 1;
  748.         hw_move(dest, source, number);
  749.         del_len = start - window->ccol;
  750.         fix_marks(window, dest, -(long)del_len);
  751.     }
  752. }
  753.  
  754. /*
  755.  * Name:    char_del_left
  756.  * Purpose: To delete the character to the left of the cursor.
  757.  * Date:    October 1, 1989
  758.  * Passed:  window:   information allowing access to the current window
  759.  * Notes:   If the cursor is at the left of the line, then combine the
  760.  *           current line with the previous one.
  761.  *          If in unindent mode, and the cursor is on the first non-blank
  762.  *           character of the line, then match the indentation of an
  763.  *           earlier line.
  764.  */
  765. void char_del_left(window)
  766. windows *window;
  767. {
  768.     int len;            /* length of the current line */
  769.     text_ptr source;    /* source of block move to delete character */
  770.     text_ptr dest;      /* destination of block move */
  771.     long number;        /* number of characters to move */
  772.     text_ptr p;         /* previous line in file */
  773.     int cr;             /* did line end with carriage return? */
  774.     int plen;           /* length of previous line */
  775.     int del_count;      /* number of characters to delete */
  776.     int pos;            /* the position of the first non-blank char */
  777.  
  778.     copy_line(window);
  779.     len = linelen(g_status.line_buff);
  780.     if (window->ccol == 0) {
  781.         /*
  782.          * combine this line with the previous, if any
  783.          */
  784.         if ((p = find_prev(window->cursor)) != NULL) {
  785.             if (g_status.line_buff[len] == '\n') {
  786.                 cr = 1;
  787.             }
  788.             else {
  789.                 cr = 0;
  790.             }
  791.             if (len + cr + (plen = linelen(p)) >= BUFF_SIZE) {
  792.                 error(WARNING, "cannot combine lines");
  793.                 return;
  794.             }
  795.             un_copy_line(window);
  796.  
  797.             /*
  798.              * do the move and fix marks
  799.              */
  800.             source = window->cursor;
  801.             dest = source-1;
  802.             number = g_status.end_mem - source;
  803.             hw_move(dest, source, number);
  804.             fix_marks(window, dest, -1L);
  805.  
  806.             /*
  807.              * adjust the cursor line, since it is now in the middle of a
  808.              *  newly formed line
  809.              */
  810.             window->cursor = dest - prelinelen(dest);
  811.  
  812.             /*
  813.              * make sure cursor stays on the screen, at the end of the
  814.              *  previous line
  815.              */
  816.             if (window->cline == window->top_line) {
  817.                 scroll_up(window);
  818.             }
  819.             window_scroll_up(window->cline, window->bottom_line);
  820.             --window->cline;
  821.             if ((window->ccol = plen) >= g_display.ncols) {
  822.                 window->ccol = g_display.ncols-1;
  823.             }
  824.         }
  825.     }
  826.     else {
  827.         /*
  828.          * normal delete
  829.          *
  830.          * find out how much to delete (depends on unindent mode)
  831.          */
  832.         del_count = 1;   /* the default */
  833.         if (g_status.unindent) {
  834.             /*
  835.              * Unindent only happens if the cursor is on the first
  836.              *  non-blank character of the line, or if the cursor is
  837.              *  beyond the end of the line.
  838.              */
  839.             if ((pos = first_non_blank(g_status.line_buff)) == window->ccol
  840.                     || g_status.line_buff[pos] == '\n'
  841.                     || g_status.line_buff[pos] == '\0') {
  842.                 /*
  843.                  * now work out how much to unindent
  844.                  */
  845.                 p = window->cursor;
  846.                 for (;;) {
  847.                     if ((p = find_prev(p)) == NULL) {
  848.                         /*
  849.                          * no more lines to try, so give up and just
  850.                          *  delete one character
  851.                          */
  852.                         break;
  853.                     }
  854.                     if ((plen = first_non_blank((char *)p)) < window->ccol &&
  855.                             *(p+plen) != '\n') {
  856.                         /*
  857.                          * found the line to match
  858.                          */
  859.                         del_count = window->ccol - plen;
  860.                         break;
  861.                     }
  862.                 }
  863.             }
  864.         }
  865.  
  866.         /*
  867.          * move text to delete char(s), unless no chars actually there
  868.          */
  869.         if (window->ccol - del_count < len) {
  870.             /*
  871.              * note that this may move characters beyond the end of the
  872.              *  line in the line buffer, but this does not matter.
  873.              */
  874.             source = g_status.line_buff + window->ccol;
  875.             dest = source - del_count;
  876.             number = strlen(g_status.line_buff) - window->ccol + 1;
  877.             hw_move(dest, source, number);
  878.             fix_marks(window, dest, -(long)del_count);
  879.         }
  880.         window->ccol -= del_count;
  881.  
  882.         /*
  883.          * give update algorithm a hint
  884.          */
  885.         if (del_count == 1) {
  886.             xygoto(window->ccol, window->cline);
  887.             c_delete();
  888.         }
  889.     }
  890. }
  891.  
  892. /*
  893.  * Name:    line_kill
  894.  * Purpose: To delete the line the cursor is on.
  895.  * Date:    October 1, 1989
  896.  * Passed:  window:   information allowing access to the current window
  897.  */
  898. void line_kill(window)
  899. windows *window;
  900. {
  901.     /*
  902.      * let copy and un_copy do most of the work. Simply record that the
  903.      *  current line has no characters in it!
  904.      */
  905.     copy_line(window);
  906.     window_scroll_up(window->cline, window->bottom_line);
  907.     fix_marks(window, g_status.line_buff, -(long)strlen(g_status.line_buff));
  908.     *g_status.line_buff = '\0';
  909.     un_copy_line(window);
  910.     window->ccol = 0;
  911. }
  912.  
  913. /*
  914.  * Name:    char_del_under
  915.  * Purpose: To delete the character under the cursor.
  916.  * Date:    October 1, 1989
  917.  * Passed:  window:   information allowing access to the current window
  918.  * Notes:   If the cursor is beyond the end of the line, then this
  919.  *           command is ignored.
  920.  */
  921. void char_del_under(window)
  922. windows *window;
  923. {
  924.     text_ptr source;    /* source of block move to delete character */
  925.     text_ptr dest;      /* destination of block move */
  926.     long number;        /* number of characters to move */
  927.  
  928.     copy_line(window);
  929.     if (window->ccol >= linelen(g_status.line_buff)) {
  930.         return;
  931.     }
  932.     else {
  933.         /*
  934.          * move text to delete char, then fix marks
  935.          */
  936.         source = g_status.line_buff + window->ccol + 1;
  937.         dest = source - 1;
  938.         number = strlen(g_status.line_buff) - window->ccol;
  939.         hw_move(dest, source, number);
  940.         fix_marks(window, dest, -1L);
  941.  
  942.         /*
  943.          * give update algorithm a hint
  944.          */
  945.         xygoto(window->ccol, window->cline);
  946.         c_delete();
  947.     }
  948. }
  949.  
  950. /*
  951.  * Name:    eol_kill
  952.  * Purpose: To delete everything from the cursor to the end of the line.
  953.  * Date:    October 1, 1989
  954.  * Passed:  window:   information allowing access to the current window
  955.  * Notes:   If the cursor is beyond the end of the line, then this
  956.  *           command is ignored.
  957.  */
  958. void eol_kill(window)
  959. windows *window;
  960. {
  961.     char *dest;  /* the start of the delete area */
  962.     int len;     /* the length of the current line */
  963.  
  964.     copy_line(window);
  965.     if (window->ccol >= (len = linelen(g_status.line_buff))) {
  966.         return;
  967.     }
  968.     else {
  969.         /*
  970.          * truncate to delete rest of line
  971.          */
  972.         dest = g_status.line_buff + window->ccol;
  973.  
  974.         /*
  975.          * the \n at the end of the line must NOT be deleted!
  976.          */
  977.         if (g_status.line_buff[len] == '\n') {
  978.             *dest++ = '\n';
  979.         }
  980.  
  981.         len = strlen(dest);
  982.         fix_marks(window, dest, (long) (-len));
  983.         *dest = '\0';
  984.     }
  985. }
  986.  
  987. /*
  988.  * Name:    reminder
  989.  * Purpose: To remind the user that we are half-way through a two-character
  990.  *           command.
  991.  * Date:    October 1, 1989
  992.  * Passed:  mess:     the text to be displayed
  993.  * Notes:   "mess" is displayed, highlighted, in the top left corner
  994.  */
  995. void reminder(mess)
  996. char *mess;
  997. {
  998.     char old_wanted;  /* previous attribute */
  999.  
  1000.     /*
  1001.      * only display message if user has not already typed the command!
  1002.      */
  1003.     if (!c_avail()) {
  1004.         xygoto(0, 0);
  1005.         old_wanted = g_status.wanted;
  1006.         set_attr(g_display.flash);
  1007.         s_output(mess);
  1008.         set_attr(old_wanted);
  1009.     }
  1010. }
  1011.  
  1012. /*
  1013.  * Name:    goto_left
  1014.  * Purpose: To move the cursor to the left of the current line.
  1015.  * Date:    October 1, 1989
  1016.  * Passed:  window:   information allowing access to the current window
  1017.  */
  1018. void goto_left(window)
  1019. windows *window;
  1020. {
  1021.     window->ccol = 0;
  1022. }
  1023.  
  1024. /*
  1025.  * Name:    goto_right
  1026.  * Purpose: To move the cursor to the right of the current line.
  1027.  * Date:    October 1, 1989
  1028.  * Passed:  window:   information allowing access to the current window
  1029.  */
  1030. void goto_right(window)
  1031. windows *window;
  1032. {
  1033.     if (g_status.copied) {
  1034.         window->ccol = linelen(g_status.line_buff);
  1035.     }
  1036.     else {
  1037.         window->ccol = linelen(window->cursor);
  1038.     }
  1039.  
  1040.     /*
  1041.      * keep cursor on screen
  1042.      */
  1043.     if (window->ccol >= g_display.ncols) {
  1044.         window->ccol = g_display.ncols-1;
  1045.     }
  1046. }
  1047.  
  1048. /*
  1049.  * Name:    goto_top
  1050.  * Purpose: To move the cursor to the top of the current window.
  1051.  * Date:    October 1, 1989
  1052.  * Passed:  window:   information allowing access to the current window
  1053.  * Notes:   If the start of the file occurs before the top of the window,
  1054.  *           then the start of the file is moved to the top of the window.
  1055.  */
  1056. void goto_top(window)
  1057. windows *window;
  1058. {
  1059.     text_ptr cursor;  /* anticipated cursor line */
  1060.  
  1061.     un_copy_line(window);
  1062.     for (; window->cline > window->top_line; window->cline--) {
  1063.         if ((cursor = find_prev(window->cursor)) == NULL) {
  1064.             window->cline = window->top_line;
  1065.             break;
  1066.         }
  1067.         window->cursor = cursor;
  1068.     }
  1069. }
  1070.  
  1071. /*
  1072.  * Name:    goto_bottom
  1073.  * Purpose: To move the cursor to the bottom of the current window.
  1074.  * Date:    October 1, 1989
  1075.  * Passed:  window:   information allowing access to the current window
  1076.  * Notes:   If the end of the file occurs before the bottom of the window,
  1077.  *           then the end of the file is moved to the bottom of the window.
  1078.  */
  1079. void goto_bottom(window)
  1080. windows *window;
  1081. {
  1082.     text_ptr cursor;
  1083.  
  1084.     un_copy_line(window);
  1085.     for (; window->cline < window->bottom_line; window->cline++) {
  1086.         if ((cursor = find_next(window->cursor)) == NULL) {
  1087.             window->cline = window->bottom_line;
  1088.             break;
  1089.         }
  1090.         window->cursor = cursor;
  1091.     }
  1092. }
  1093.  
  1094. /*
  1095.  * Name:    set_tabstop
  1096.  * Purpose: To set the current interval between tab stops
  1097.  * Date:    October 1, 1989
  1098.  * Notes:   Tab interval must be reasonable, and this function will
  1099.  *           not allow tabs more than MAX_COLS / 2.
  1100.  */
  1101. void set_tabstop()
  1102. {
  1103.     char num_str[MAX_COLS];  /* tab interval as a character string */
  1104.     int tab;                 /* new tab interval */
  1105.  
  1106.     for (;;) {
  1107.         sprintf(num_str, "%d", g_status.tab_size);
  1108.         if (get_name("Tab interval: ", 1, num_str) != OK) {
  1109.             return;
  1110.         }
  1111.         tab = atoi(num_str);
  1112.         if (tab < MAX_COLS/2) {
  1113.             break;
  1114.         }
  1115.     }
  1116.     g_status.tab_size = tab;
  1117. }
  1118.  
  1119. /*
  1120.  * Name:    command
  1121.  * Purpose: To input and execute a command or printable character.
  1122.  * Date:    October 1, 1989
  1123.  * Passed:  window:   information allowing access to the current window
  1124.  * Returns: nothing really, but needs to be compatible with other functions
  1125.  *           of type do_func.
  1126.  * Notes:   This enormous string of nested ifs is not a very modular
  1127.  *           way of doing things. Eventually, the user should probably
  1128.  *           be allowed to change the command keys without needing to
  1129.  *           recompile the source code. For the present, at least this
  1130.  *           keeps everything in the one place so it is easy to change!
  1131.  */
  1132. int command(window)
  1133. windows *window;
  1134. {
  1135.     int c;   /* character entered */
  1136.  
  1137.     if (hw_printable(c = c_input())) {
  1138.         /*
  1139.          * if a printable character, simply insert it
  1140.          */
  1141.         insert(window, c, TRUE);
  1142.     }
  1143.     else if (c == '\t') {
  1144.         tab_key(window);
  1145.     }
  1146.     else if (c == '\b') {
  1147.         char_del_left(window);
  1148.     }
  1149.     else if (c == CONTROL('G') || c == 127) {
  1150.         char_del_under(window);
  1151.     }
  1152.     else if (c == '\r') {
  1153.         insert(window, '\n', TRUE);
  1154.     }
  1155.     else if (c == CONTROL('N')) {
  1156.         insert(window, '\n', FALSE);
  1157.     }
  1158.     else if (c == CONTROL('L')) {
  1159.         do_last(window);
  1160.     }
  1161.     else if (c == CONTROL('Z')) {
  1162.         scroll_down(window);
  1163.     }
  1164.     else if (c == CONTROL('W')) {
  1165.         scroll_up(window);
  1166.     }
  1167.     else if (c == CONTROL('X')) {
  1168.         move_down(window);
  1169.     }
  1170.     else if (c == CONTROL('E')) {
  1171.         move_up(window);
  1172.     }
  1173.     else if (c == CONTROL('S')) {
  1174.         move_left(window);
  1175.     }
  1176.     else if (c == CONTROL('D')) {
  1177.         move_right(window);
  1178.     }
  1179.     else if (c == CONTROL('C')) {
  1180.         page_down(window);
  1181.     }
  1182.     else if (c == CONTROL('R')) {
  1183.         page_up(window);
  1184.     }
  1185.     else if (c == CONTROL('F')) {
  1186.         word_right(window);
  1187.     }
  1188.     else if (c == CONTROL('A')) {
  1189.         word_left(window);
  1190.     }
  1191.     else if (c == CONTROL('T')) {
  1192.         word_delete(window);
  1193.     }
  1194.     else if (c == CONTROL('Y')) {
  1195.         line_kill(window);
  1196.     }
  1197.     else if (c == CONTROL('V')) {
  1198.         g_status.insert = !g_status.insert;
  1199.     }
  1200.     else if (c == CONTROL('J')) {
  1201.         get_help(window);
  1202.     }
  1203.     else if (c == CONTROL('\\')) {
  1204.         force_blank();
  1205.     }
  1206.     else if (c == CONTROL('K')) {
  1207.         reminder("^K");
  1208.         if ((c = c_input()) >= '0' && c <= '9') {
  1209.             set_marker(window, c - '0');
  1210.         }
  1211.         else {
  1212.             c = CONTROL(c);
  1213.             if (c == CONTROL('Q')) {
  1214.                 quit(window);
  1215.             }
  1216.             else if (c == CONTROL('S')) {
  1217.                 save_file(window, SAVE_NORMAL);
  1218.             }
  1219.             else if (c == CONTROL('T')) {
  1220.                 save_as_file(window);
  1221.             }
  1222.             else if (c == CONTROL('X')) {
  1223.                 if (window->file_info->modified) {
  1224.                     save_file(window, SAVE_NORMAL);
  1225.                 }
  1226.  
  1227.                 /*
  1228.                  * The following check picks up the case where for some
  1229.                  *  reason the file could not be written. In such a case,
  1230.                  *  the last thing we want to do is quit the editor!
  1231.                  */
  1232.                 if (!window->file_info->modified) {
  1233.                     finish(window);
  1234.                 }
  1235.             }
  1236.             else if (c == CONTROL('D')) {
  1237.                 save_file(window, SAVE_NORMAL);
  1238.                 if (!window->file_info->modified) {
  1239.                     finish(window);
  1240.                 }
  1241.             }
  1242.             else if (c == CONTROL('B')) {
  1243.                 mark_start(window);
  1244.             }
  1245.             else if (c == CONTROL('K')) {
  1246.                 mark_end(window);
  1247.             }
  1248.             else if (c == CONTROL('H')) {
  1249.                 window->file_info->visible = !window->file_info->visible;
  1250.             }
  1251.             else if (c == CONTROL('V')) {
  1252.                 block_move(window);
  1253.             }
  1254.             else if (c == CONTROL('Y')) {
  1255.                 block_delete(window);
  1256.             }
  1257.             else if (c == CONTROL('C')) {
  1258.                 block_copy(window);
  1259.             }
  1260.             else if (c == CONTROL('I')) {
  1261.                 block_indent(window);
  1262.             }
  1263.             else if (c == CONTROL('U')) {
  1264.                 block_unindent(window);
  1265.             }
  1266.             else if (c == CONTROL('R')) {
  1267.                 block_read(window, FALSE);
  1268.             }
  1269.             else if (c == CONTROL('@')) {
  1270.                 block_read(window, TRUE);
  1271.             }
  1272.             else if (c == CONTROL('W')) {
  1273.                 block_write(window);
  1274.             }
  1275.             else if (c == CONTROL('P')) {
  1276.                 block_print(window);
  1277.             }
  1278.             else if (c == CONTROL('F')) {
  1279.                 os_shell();
  1280.             }
  1281.             else {
  1282.                 error(WARNING, "unknown command: ^K^%c",
  1283.                         CONTROL(c)+'A'-1);
  1284.             }
  1285.         }
  1286.     }
  1287.     else if (c == CONTROL('Q')) {
  1288.         reminder("^Q");
  1289.         if ((c = c_input()) >= '0' && c <= '9') {
  1290.             goto_marker(window, c - '0');
  1291.         }
  1292.         else {
  1293.             c = CONTROL(c);
  1294.             if (c == CONTROL('L')) {
  1295.                 g_status.copied = FALSE;
  1296.             }
  1297.             else if (c == CONTROL('Y')) {
  1298.                 eol_kill(window);
  1299.             }
  1300.             else if (c == CONTROL('F')) {
  1301.                 find_string(window);
  1302.             }
  1303.             else if (c == CONTROL('A')) {
  1304.                 replace_string(window);
  1305.             }
  1306.             else if (c == CONTROL('P')) {
  1307.                 goto_marker(window, PREVIOUS);
  1308.             }
  1309.             else if (c == CONTROL('B')) {
  1310.                 goto_marker(window, START_BLOCK);
  1311.             }
  1312.             else if (c == CONTROL('K')) {
  1313.                 goto_marker(window, END_BLOCK);
  1314.             }
  1315.             else if (c == CONTROL('S')) {
  1316.                 goto_left(window);
  1317.             }
  1318.             else if (c == CONTROL('D')) {
  1319.                 goto_right(window);
  1320.             }
  1321.             else if (c == CONTROL('E')) {
  1322.                 goto_top(window);
  1323.             }
  1324.             else if (c == CONTROL('X')) {
  1325.                 goto_bottom(window);
  1326.             }
  1327.             else if (c == CONTROL('R')) {
  1328.                 goto_top_file(window);
  1329.             }
  1330.             else if (c == CONTROL('C')) {
  1331.                 goto_end_file(window);
  1332.             }
  1333.             else if (c == CONTROL('[')) {
  1334.                 match_pair(window, TRUE);
  1335.             }
  1336.             else if (c == CONTROL(']')) {
  1337.                 match_pair(window, FALSE);
  1338.             }
  1339.             else if (c == CONTROL('I')) {
  1340.                 goto_line(window);
  1341.             }
  1342.             else {
  1343.                 error(WARNING, "unknown command: ^Q^%c",
  1344.                         CONTROL(c)+'A'-1);
  1345.             }
  1346.         }
  1347.     }
  1348.     else if (c == CONTROL('O')) {
  1349.         reminder("^O");
  1350.         if ((c = CONTROL(c_input())) == CONTROL('I')) {
  1351.             g_status.indent = !g_status.indent;
  1352.         }
  1353.         else if (c == CONTROL('U')) {
  1354.             g_status.unindent = !g_status.unindent;
  1355.         }
  1356.         else if (c == CONTROL('T')) {
  1357.             set_tabstop();
  1358.         }
  1359.         else if (c == CONTROL('K')) {
  1360.             choose_window(NULL, window);
  1361.         }
  1362.         else if (c == CONTROL('M')) {
  1363.             size_window(window);
  1364.         }
  1365.         else {
  1366.             error(WARNING, "unknown command: ^O^%c",
  1367.                     CONTROL(c)+'A'-1);
  1368.         }
  1369.     }
  1370.     else if (c == 0) {
  1371.         /*
  1372.          * ignore
  1373.          */
  1374.         ;
  1375.     }
  1376.     else {
  1377.         error(WARNING, "illegal (unprintable) key: %d", c);
  1378.     }
  1379.     xygoto(g_status.current_window->ccol, g_status.current_window->cline);
  1380.  
  1381.     /*
  1382.      * check for time to auto-save
  1383.      */
  1384.     if (g_status.unsaved) {
  1385.         if (time(NULL) > g_status.save_interval + g_status.save_time) {
  1386.             save_file(g_status.current_window, SAVE_RECOVERY);
  1387.             g_status.save_time += g_status.save_interval;
  1388.         }
  1389.     }
  1390.  
  1391.     return 0;  /* no meaning here */
  1392. }
  1393.  
  1394. /*
  1395.  * Name:    editor
  1396.  * Purpose: To allow the full-screen editing of plain text files, in a way
  1397.  *           that is effective over slow serial communication lines.
  1398.  * Date:    October 1, 1989
  1399.  * Passed:  argc:   number of command line arguments
  1400.  *          argv:   text of command line arguments
  1401.  * Notes:   This is a separate function, rather than the main program, in
  1402.  *           case the hardware dependent implementation needs extra
  1403.  *           command line parameters etc.
  1404.  */
  1405. void editor(argc, argv)
  1406. int argc;
  1407. char *argv[];
  1408. {
  1409.     char *name;  /* name of file to start editing */
  1410.  
  1411.     /*
  1412.      * set up the screen
  1413.      */
  1414.     initialize();
  1415.  
  1416.     /*
  1417.      * Check that user specified file to edit, if not offer help
  1418.      */
  1419.     if (argc != 2) {
  1420.         name = g_status.help_file;
  1421.     }
  1422.     else {
  1423.         name = argv[1];
  1424.     }
  1425.  
  1426.     /*
  1427.      * If a recovery file exists from a previous editing session,
  1428.      *  then perform recovery automatically.
  1429.      */
  1430.     hw_copy_path(name, RECOVERY, g_status.recovery);
  1431.     if (hw_fattrib(g_status.recovery) != ERROR) { /* file exists */
  1432.         error(DIAG, "Recovering previous file");
  1433.         if (open_window(NULL, g_status.recovery) == ERROR) {
  1434.             terminate();
  1435.             exit(1);
  1436.         }
  1437.         /*
  1438.          * record no default file name when saving, and force user to save
  1439.          */
  1440.         g_status.file_list->file_name[0] = '\0';
  1441.         g_status.file_list->modified = TRUE;
  1442.     }
  1443.     else if (open_window(NULL, name) == ERROR) {
  1444.         /*
  1445.          * could not open window for normal edit file - out of memory?
  1446.          */
  1447.         terminate();
  1448.         exit(1);
  1449.     }
  1450.  
  1451.     /*
  1452.      * main loop - keep updating the display and processing any commands
  1453.      *  forever!
  1454.      */
  1455.     for (;;) {
  1456.         (void) display(command, 0);
  1457.     }
  1458. }
  1459.